﻿using Microsoft.VisualBasic;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace Drive.LiveExcelHost
{
    public class Request
    {
        public string HttpHeader { get; set; }
        public Stream Stream { get; set; }

        Dictionary<string, string> _headers;

        public Dictionary<string, string> Parameters { get; }

        public Dictionary<string, string> Headers
        {
            get
            {
                return _headers;
            }
        }

        Dictionary<string, string> _cookies;

        public Request(Stream stream, string httpHeader)
        {
            Stream = stream;
            HttpHeader = httpHeader;
            if (string.IsNullOrEmpty(httpHeader))
            {
                this.isBadRequest = true;
                return;
            }
            var headerBody = httpHeader.Split("\r\n\r\n", 2);
            var headers = headerBody[0]?.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)
                        .Select(x => x.Split(": ", 2))
                        .ToDictionary(x => x[0], x => x.Length == 2 ? x[1] : "") ?? new Dictionary<string, string>();

            if (headers.Any())
            {
                var line = headers.Keys.First().Split(' ');
                this.Method = line[0];
                this.PathAndQuery = line[1];
                this.Path = line[1].Split('?', 2)[0];
                this.QueryString = this.PathAndQuery.Substring(this.Path.Length);
                this.Version = line[2];
                if (headerBody.Length > 1)
                {
                    this.Payload = headerBody[1];
                }
            }
            _headers = headers;

            if (this.QueryString.StartsWith('?'))
            {
                this.Parameters = QueryString?.Substring(1, this.QueryString.Length - 1).Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries)
                        .Select(x => x.Split("=", 2))
                        .ToDictionary(x => x[0], x => x.Length == 2 ? x[1] : "") ?? new Dictionary<string, string>();
            }
            else
                this.Parameters = QueryString?.Split(new string[] { "&" }, StringSplitOptions.RemoveEmptyEntries)
                            .Select(x => x.Split("=", 2))
                            .ToDictionary(x => x[0], x => x.Length == 2 ? x[1] : "") ?? new Dictionary<string, string>();
        }

        public string GetHeaderValue(string name)
        {
            var key = Headers.Keys.FirstOrDefault(x => x.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (key != null)
            {
                return this.Headers[key];
            }
            return null;
        }

        public string GetParameter(string name)
        {
            var key = Parameters.Keys.FirstOrDefault(x => x.Equals(name, StringComparison.OrdinalIgnoreCase));
            if (key != null)
            {
                return this.Parameters[key];
            }
            return null;
        }
        public Dictionary<string, string> Cookies
        {
            get
            {
                var key = Headers.Keys.FirstOrDefault(x => x.Equals("cookie", StringComparison.OrdinalIgnoreCase));
                if (key != null)
                {
                    if (_cookies != null)
                        return _cookies;
                    _cookies = new Dictionary<string, string>();
                    foreach (var item in Headers[key].Split("; ").Select(x => x.Split('=', 2)))
                    {
                        if (!_cookies.TryAdd(item[0], item[1]))
                        {
                            _cookies[item[0]] = item[1];
                        }
                    }
                    return _cookies;
                }
                return (_cookies = new Dictionary<string, string>());
            }
        }

        public string PathAndQuery { get; set; }
        public string Path { get; set; }
        public string QueryString { get; }
        public string Version { get; private set; }

        public string Host
        {
            get
            {
                var key = Headers.Keys.FirstOrDefault(x => x.Equals("host", StringComparison.OrdinalIgnoreCase));
                if (key != null)
                {
                    return this.Headers[key];
                }
                throw new KeyNotFoundException();
            }
        }

        public string Origin
        {
            get
            {
                var key = Headers.Keys.FirstOrDefault(x => x.Equals("origin", StringComparison.OrdinalIgnoreCase));
                if (key != null)
                {
                    return this.Headers[key];
                }
                return null;
            }
        }

        public string Method { get; private set; }
        public string Payload { get; }
        public string Url
        {
            get
            {
                return "//" + this.Host + this.PathAndQuery;
            }
        }

        public string UrlReferer
        {
            get
            {
                return this.GetHeaderValue("referer");
            }
        }

        public string HostIpAddress { get; internal set; }
        public string UserAgent
        {
            get
            {
                var key = Headers.Keys.FirstOrDefault(x => x.Equals("User-Agent", StringComparison.OrdinalIgnoreCase));
                if (key != null)
                {
                    return this.Headers[key];
                }
                return null;
            }
        }

        public bool isBadRequest { get; }

        public Response CreateResponse()
        {
            return new Response() { Stream = this.Stream };
        }

        public Response CreateResponse(int statusCode)
        {
            return new Response { Stream = this.Stream, StatusCode = statusCode };
        }
    }
    public class Response
    {
        public Response()
        {
            this.OutputStream = new MemoryStream();
            this.Headers = new Dictionary<string, string>();
            this.Cookies = new List<System.Net.Cookie>();
            this.StatusCode = 200;
            this.StatusDescription = "OK";
        }

        /// <summary>
        /// network stream
        /// </summary>
        public Stream Stream { get; set; }
        private bool headerIsWritten;

        public void WriteString(string value, string contentType = null)
        {
            this.OutputStream.Write(value);

            if (!string.IsNullOrEmpty(contentType))
                this.Headers["Content-Type"] = contentType;
        }


        public void WriteJson(object obj)
        {
            if (obj == null)
                return;
            var val = JsonConvert.SerializeObject(obj);
            this.OutputStream.Write(val);
            this.Headers["Content-Type"] = "application/json";
        }

        public void Write(byte[] d, string contentType = "application/octet-stream")
        {
            this.OutputStream.Write(d);
            this.Headers["Content-Type"] = contentType;
        }

        public Dictionary<string, string> Headers { get; set; }

        /// <summary>
        /// output body stream
        /// </summary>
        public Stream OutputStream { get; set; }
        public int StatusCode { get; internal set; }
        public string StatusDescription { get; private set; }
        public List<System.Net.Cookie> Cookies { get; internal set; }

        private void OutputHeader()
        {
            headerIsWritten = true;
            if (StatusCode == 404)
                StatusDescription = "Not Found";
            if (StatusCode == 500)
                StatusDescription = "Internal Server Error";
            Stream.WriteLine($"HTTP/1.1 {StatusCode} {StatusDescription}");
            this.Headers["Content-Length"] = this.OutputStream.Length.ToString();
            foreach (var item in Headers)
            {
                if (item.Value != null)
                {
                    Stream.WriteLine($"{item.Key}: {item.Value}");
                }
            }
            foreach (var item in Cookies)
            {
                Stream.WriteLine($"Set-Cookie: {item.Name}={item.Value}; Path={item.Path};" + (item.HttpOnly ? " HttpOnly" : ""));
            }
            Stream.WriteLine();
        }

        public void Flush()
        {
            if (!headerIsWritten)
            {
                OutputHeader();
            }
            if (this.OutputStream.Length > 0)
            {
                this.OutputStream.Position = 0;
                this.OutputStream.CopyTo(Stream);
                this.OutputStream.Flush();
            }
        }
        public async Task FlushAsync()
        {
            if (!headerIsWritten)
            {
                OutputHeader();
            }
            if (this.OutputStream.Length > 0)
            {
                this.OutputStream.Position = 0;
                this.OutputStream.CopyTo(Stream);
                await this.OutputStream.FlushAsync();
            }
        }
        public void Dispose()
        {
            this.OutputStream.Dispose();
        }
    }
}
